#include "game.h"
#include "state.h"
#include "graphics.h"
#include "input.h"
#include "globals.h"
#include "random.h"
#include "objectmanager.h"
#include "config.h"
#include "stats.h"
#include "mainmenu.h"
#include "mapeditor.h"
#include "playserver.h"
#include "connect.h"
#include "account.h"
#include "playclient.h"
#include "characters.h"
#include "createcharacter.h"
#include "packetstream.h"

#ifdef _WIN32
	#define WIN32_LEAN_AND_MEAN
	#include <windows.h>
	#include <winbase.h>
#else
	#include <unistd.h>
#endif

// Processes parameters and initializes the game
bool CGame::Init(int argc, char **argv) 
{
	WindowActive = true;
	LocalServerRunning = false;

	E_DRIVER_TYPE DriverType = EDT_OPENGL;

	// Process arguments
	stringc Token;
	int TokensRemaining;

	Connection = 0;
	Peer = 0;

	SinglePeer.address.host = 0;
	SinglePeer.address.port = 0;

	m_GameType = GT_CLIENT;
	
	bool ok = true;

	for(int i = 1; i < argc; i++) 
	{
		Token = stringc(argv[i]);
		TokensRemaining = argc - i - 1;
		if(Token == "-host") 
		{
			m_GameType = GT_DDS;
			DriverType = EDT_NULL;
		}
		else 
		if(Token == "-mapeditor") 
		{
			m_GameType = GT_MAPEDITOR;
		}
		else
		if(Token == "-connect") 
		{
			m_GameType = GT_CONNECT;
		}
		else
		if(Token == "-login" && TokensRemaining > 1) 
		{
			AccountState::Instance().SetLoginInfo(argv[++i], argv[++i]);
		}
	}

	// Initialize config system
	if(!ConfigClass::Instance().Init())
		return false;

	ConfigClass::Instance().LoadSettings();

	// Initialize graphics system
	if(!GraphicsClass::Instance().Init(800, 600, false, DriverType, &InputClass::Instance()))
		return false;

	// Set random seed
	RandomClass::Instance().SetSeed(irrTimer->getRealTime());

	// Start the state timer
	ResetTimer();

	// Set up the stats system
	if(!StatsClass::Instance().Init())
		return false;

	if(m_GameType == GT_CLIENT || m_GameType == GT_CONNECT)
	{

		// Initialize input system
		if(!InputClass::Instance().Init())
			return false;

		ok &= RegisterState("GS_MAINMENU",MainMenuState::Ptr());
		ok &= RegisterState("GS_CONNECT",ConnectState::Ptr());
		ok &= RegisterState("GS_CREATECHAR",CreateCharacterState::Ptr());
		ok &= RegisterState("GS_CLIENT",PlayClientState::Ptr());
		ok &= RegisterState("GS_ACCOUNT",AccountState::Ptr());
		ok &= RegisterState("GS_CHARACTER",CharactersState::Ptr());

		if(m_GameType == GT_CONNECT)
			ok &= SwitchState("GS_CONNECT");
		else
			ok &= SwitchState("GS_MAINMENU");
	}
	else
	if(m_GameType == GT_MAPEDITOR)
	{
		// Initialize input system
		if(!InputClass::Instance().Init())
			return false;

		ok &= RegisterState("GS_MAPEDITOR",MapEditorState::Ptr());
		ok &= SwitchState("GS_MAPEDITOR");
	}
	else
	if(m_GameType == GT_DDS)
	{
		PlayServerState::Instance().Init(true);
	}

	m_NeedExit = false;

	if(!ok)
		m_NeedExit = true;

	return ok;
}

bool CGame::RegisterState(stringc _name,CState* _state)
{
	if(!_state)
		return false;

	bool ok = _state->Init();

	return ok && m_StatesMap.insert(_name,_state);
}

bool CGame::PushState(stringc _stname)
{
	bool ok = true;

	if(m_StatesMap.find(_stname))
	{
		CState* st = m_StatesMap[_stname];
		m_StateStack.push_front(st);
		return st->Show();
	}
	else
		return false;
}

bool CGame::PopState()
{
	bool ok = true;

	list<CState*>::Iterator it = m_StateStack.begin();
	if(it != m_StateStack.end())
	{
		CState* st = (*it);

		if(st)
			ok &= st->Hide();
		else
			ok = false;

		m_StateStack.erase(it);
	}

	return ok;
}

bool CGame::PopAll()
{
	bool ok = true;

	list<CState*>::Iterator it = m_StateStack.begin();
	while(it!=m_StateStack.end())
	{
		CState* st = (*it);

		if(st)
			ok &= st->Hide();
		else
			ok = false;

		it = m_StateStack.erase(it);
	}

	return ok;
}

bool CGame::SwitchState(stringc _stname)
{
	PopAll();

	ResetGraphics();
	ResetTimer();
	InputClass::Instance().ResetInputState();

	PushState(_stname);

	return true;
}

CState* CGame::CurrentState()
{
	list<CState*>::Iterator it = m_StateStack.begin();
	if(it != m_StateStack.end())
		return (*it);

	return 0;
}

// Shuts down the game
bool CGame::Close() 
{
	irr::core::map<stringc,CState*>::Iterator it = m_StatesMap.getIterator();
	it.reset();

	bool ok = true;

	while(!it.atEnd())
	{
		CState* st = (*it).getValue();

		if(st)
			ok &= st->Close();
		else
			ok = false;

		it++;
	}

	m_StatesMap.clear();
	m_StateStack.clear();

	if(LocalServerRunning)
		PlayServerState::Instance().Close();

	WaitForDisconnect();
	ConfigClass::Instance().SaveSettings();

	StatsClass::Instance().Close();
	InputClass::Instance().Close();
	GraphicsClass::Instance().Close();
	ConfigClass::Instance().Close();

	return ok;
}

// Updates the current state and manages the state stack
void CGame::Run() 
{
	while(!m_NeedExit)
	{
		// Run irrlicht engine
		if(!irrDevice->run())
			m_NeedExit = true;

		// Get time difference from last frame
		DeltaTime = irrTimer->getTime() - TimeStamp;
		TimeStamp = irrTimer->getTime();

		// Limit frame rate
		if(DeltaTime < 15)
			Delay(15 - DeltaTime);

		if(m_GameType == GT_DDS)
		{
			PlayServerState::Instance().Update(DeltaTime);
		}
		else
		{
			// Check for window activity
			PreviousWindowActive = WindowActive;
			WindowActive = irrDevice->isWindowActive();

			// Check for window focus/blur events
			if(PreviousWindowActive != WindowActive) 
			{
				InputClass::Instance().ResetInputState();
			}

			if(LocalServerRunning)
			{
				PlayServerState::Instance().Update(DeltaTime);
			}
			else
			if(Connection)
			{
				CState *State = CurrentState();

				ENetEvent Event;
				while(enet_host_service(Connection, &Event, 0) > 0) 
				{
					if(Event.type == ENET_EVENT_TYPE_DISCONNECT)
					{
						State->HandleDisconnect(&Event);

						Peer = 0;

						if(Connection)
						{
							enet_host_destroy(Connection);
							Connection = 0;
						}
						enet_deinitialize();

						break;
					}
					else
					if(Event.type == ENET_EVENT_TYPE_RECEIVE)
					{
						State->HandlePacket(&Event);
						enet_packet_destroy(Event.packet);
					}
				}
			}

			if(CurrentState())
				CurrentState()->Update(DeltaTime);

			// Draw
			GraphicsClass::Instance().BeginFrame();

			list<CState*>::Iterator it = m_StateStack.begin();
			while(it!=m_StateStack.end())
			{
				CState* st = (*it);

				if(st)
					st->Draw();

				it++;
			}
			GraphicsClass::Instance().EndFrame();
		}
	}
}

// Resets the game timer
void CGame::ResetTimer() 
{
	irrTimer->setTime(0);
	TimeStamp = irrTimer->getTime();
}

// Resets the graphics for a state
void CGame::ResetGraphics() 
{
	GraphicsClass::Instance().Clear();
}

// Delays execution of the program
void CGame::Delay(int TTime) 
{
	#ifdef _WIN32
		Sleep(TTime);
	#else
		usleep(TTime * 1000);
	#endif
}

// Starts the local server
void CGame::StartLocalServer() 
{
	if(!LocalServerRunning) 
	{
		LocalServerRunning = true;

		PlayServerState::Instance().Init(false);
	}
}

// Stops the local server
void CGame::StopLocalServer() 
{
	if(LocalServerRunning) 
	{
		LocalServerRunning = false;

		PlayServerState::Instance().Close();
	}
}

bool CGame::Connect(const char *TIPAddress) 
{
	if(LocalServerRunning)
	{
		// Simulate the connect
		ENetEvent Event;
		Event.peer = &SinglePeer;
		PlayServerState::Instance().HandleConnect(&Event);
		CurrentState()->HandleConnect(&Event);

		return true;
	}
	else
	{
		if(Connection)
			return false;

		// Initialize enet
		if(enet_initialize() == -1)
			return false;

		// Create connection
		Connection = enet_host_create(NULL, 1, 0, 0, 0);
		
		if(!Connection)
			return false;

		// Get server address
		ENetAddress Address;
		enet_address_set_host(&Address, TIPAddress);
		Address.port = NETWORKING_PORT;

		// Connect to server
		Peer = enet_host_connect(Connection, &Address, 2, 0);

		if(!Peer) 
			return false;

		return true;
	}
}

void CGame::Disconnect()
{
	if(LocalServerRunning)
	{
		ENetEvent Event;
		Event.peer = &SinglePeer;
		PlayServerState::Instance().HandleDisconnect(&Event);
		CurrentState()->HandleDisconnect(&Event);
		return;
	}

	if(Peer)
	{
		enet_peer_disconnect(Peer, 0);
		Peer = 0;
	}
}

void CGame::WaitForDisconnect() 
{
	if(LocalServerRunning)
		return;

	if(Peer) 
	{
		ENetEvent Event;
		while(enet_host_service(Connection, &Event, 1000) > 0) 
		{
			if(Event.type == ENET_EVENT_TYPE_DISCONNECT) 
			{
				Peer = 0;
				if(Connection)
				{
					enet_host_destroy(Connection);
				}
				Connection = 0;

				enet_deinitialize();
				return;
			}
		}
	}
}

void CGame::SendPacketToHost(PacketClass *TPacket)
{
	if(LocalServerRunning)
	{
		ENetEvent Event;
		Event.peer = &SinglePeer;
		Event.packet = TPacket->GetENetPacket();
		PlayServerState::Instance().HandlePacket(&Event);
		enet_packet_destroy(Event.packet);
		return;
	}

	if(!Peer)
		return;

	// Resize
	TPacket->Shrink();

    // Send packet
	enet_peer_send(Peer, TPacket->GetChannel(), TPacket->GetENetPacket());
}